﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Artek.Core.Controls
{
    public class CircleProgressBar : Canvas
    {
        public static readonly DependencyProperty IsValueShowProperty =
             DependencyProperty.Register("IsValueShow", typeof(bool),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(true, OnValueVisibleChanged));

        public static readonly DependencyProperty InPercentProperty =
             DependencyProperty.Register("InPercent", typeof(bool),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(true, null));

        public static readonly DependencyProperty ValueProperty =
             DependencyProperty.Register("Value", typeof(double),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(180d, OnValueChanged));

        public static readonly DependencyProperty MinimumProperty =
             DependencyProperty.Register("Minimum", typeof(double),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(0d, OnValueChanged));


        public static readonly DependencyProperty MaximumProperty =
             DependencyProperty.Register("Maximum", typeof(double),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(360d, OnValueChanged));

        public static readonly DependencyProperty BackgroundCircleStrokeProperty =
             DependencyProperty.Register("BackgroundCircleStroke", typeof(Brush),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(Brushes.Gray, OnStrokeChanged));

        public static readonly DependencyProperty BackgroundCircleStrokeThicknessProperty =
             DependencyProperty.Register("BackgroundCircleStrokeThickness", typeof(double),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(5d, OnStrokeChanged));

        public static readonly DependencyProperty MainCircleStrokeProperty =
             DependencyProperty.Register("MainCircleStroke", typeof(Brush),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(Brushes.DeepSkyBlue, OnStrokeChanged));

        public static readonly DependencyProperty MainCircleStrokeThicknessProperty =
             DependencyProperty.Register("MainCircleStrokeThickness", typeof(double),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(5d, OnStrokeChanged));

        public static readonly DependencyProperty TextStrokeProperty =
             DependencyProperty.Register("TextStroke", typeof(Brush),
             typeof(CircleProgressBar), new FrameworkPropertyMetadata(Brushes.White, OnStrokeChanged));

        #region PUBLIC
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }
        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }
        public Brush BackgroundCircleStroke
        {
            get { return (Brush)GetValue(BackgroundCircleStrokeProperty); }
            set { SetValue(BackgroundCircleStrokeProperty, value); }
        }
        public Brush MainCircleStroke
        {
            get { return (Brush)GetValue(MainCircleStrokeProperty); }
            set { SetValue(MainCircleStrokeProperty, value); }
        }
        public Brush TextStroke
        {
            get { return (Brush)GetValue(TextStrokeProperty); }
            set { SetValue(TextStrokeProperty, value); }
        }
        public double BackgroundCircleStrokeThickness
        {
            get { return (double)GetValue(BackgroundCircleStrokeThicknessProperty); }
            set { SetValue(BackgroundCircleStrokeThicknessProperty, value); }
        }
        public double MainCircleStrokeThickness
        {
            get { return (double)GetValue(MainCircleStrokeThicknessProperty); }
            set { SetValue(MainCircleStrokeThicknessProperty, value); }
        }

        public bool IsValueShow
        {
            get { return (bool) GetValue(IsValueShowProperty); }
            set
            {
                SetValue(IsValueShowProperty, value);
            }
        }

        public bool InPercent
        {
            get { return (bool) GetValue(InPercentProperty); }
            set
            {
                SetValue(InPercentProperty, value);
            }
        }
        #endregion

        #region PRIVATE
        private readonly Path _backEllipse = new Path()
        {
            StrokeThickness = 5,
            Stroke = Brushes.Gray
        };

        private readonly Path _mainEllipse = new Path()
        {
            StrokeThickness = 5,
            Stroke = Brushes.DeepSkyBlue
        };

        private readonly Path _text = new Path()
        {
            StrokeThickness = 1,
            Fill = Brushes.Black
        };


        private double _radius = 10;
        private Point _center = new Point(0, 0);
        private Point _startPoint = new Point(0, 0);
        #endregion


        public CircleProgressBar()
        {
            _backEllipse.Data = new EllipseGeometry(new Point(Width / 2, Height / 2), _radius, _radius);
            _mainEllipse.Data = new PathGeometry()
            {
                Figures = new PathFigureCollection()
                {
                    new PathFigure(new Point(Width/2, 0), new PathSegmentCollection()
                    {
                        new ArcSegment(
                            (new RotateTransform(Value*(360/(Maximum - Minimum)), _center.X, _center.Y)).Transform(
                                _startPoint), new Size(_radius*2, _radius*2), 0, true, SweepDirection.Clockwise, false)
                    }, false)
                }
            };
            TextStroke = Brushes.Black;
            var text = new FormattedText(Value.ToString(CultureInfo.CurrentCulture),
                CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                new Typeface("Palatino"),
                Math.Abs((0.8 * _radius)) < 0.00001 ? 1 : (0.8 * _radius),
                TextStroke);

            _text.Data = text.BuildGeometry(new Point(_center.X - text.Width / 2, _center.Y - text.Height / 2));
            _text.Fill = TextStroke;
            _text.Visibility = Visibility.Visible;
            Children.Add(_backEllipse);
            Children.Add(_mainEllipse);
            Children.Add(_text);
            SizeChanged += OnSizeChanged;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            var quadSize = e.NewSize.Height <= e.NewSize.Width ? e.NewSize.Height : e.NewSize.Width;
            _radius = quadSize / 2;
            _center = new Point(e.NewSize.Width / 2, e.NewSize.Height / 2);
            _startPoint = _center - new Vector(0, _radius);

            UpdateCircle(this);
        }

        /// <summary>
        /// Action when Value, Minimum or Maximum changed.
        /// </summary>
        /// <param name="d">Dependecy object.</param>
        /// <param name="e">EventArgs.</param>
        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var circle = d as CircleProgressBar;

            UpdateCircle(circle);
        }

        private static void OnStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var circle = d as CircleProgressBar;
            circle._backEllipse.Stroke = circle.BackgroundCircleStroke;
            circle._backEllipse.StrokeThickness = circle.BackgroundCircleStrokeThickness;

            circle._mainEllipse.Stroke = circle.MainCircleStroke;
            circle._mainEllipse.StrokeThickness = circle.MainCircleStrokeThickness;

            circle._text.Fill = circle.TextStroke;
        }

        /// <summary>
        /// Update Background and Main circles.
        /// </summary>
        /// <param name="circle">Reference to CircleProgressBar control.</param>
        private static void UpdateCircle(CircleProgressBar circle)
        {
            circle._backEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);

            if (Math.Abs(circle.Value * (360 / (circle.Maximum - circle.Minimum)) - 360) < 0.0001)
                circle._mainEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);
            else
            {
                circle._mainEllipse.Data = new PathGeometry()
                {
                    Figures = new PathFigureCollection()
                    {
                        new PathFigure(circle._startPoint, new PathSegmentCollection()
                        {
                            new ArcSegment(
                                (new RotateTransform(circle.Value*(360/(circle.Maximum - circle.Minimum)),
                                    circle._center.X, circle._center.Y)).Transform(
                                        circle._startPoint), new Size(circle._radius, circle._radius), 0,
                                !(circle.Value*(360/(circle.Maximum - circle.Minimum)) <= 180), SweepDirection.Clockwise,
                                true)
                        }, false)
                    }
                };
            }

            if (circle.IsValueShow)
            {
                circle._text.Visibility = Visibility.Visible;
                var text = new FormattedText($"{circle.Value:#}",
                CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                new Typeface("Palatino"),
                Math.Abs((0.8 * circle._radius)) < 0.00001 ? 1 : (0.8 * circle._radius),
                circle.TextStroke);

                circle._text.Fill = circle.TextStroke;
                circle._text.Data = text.BuildGeometry(new Point(circle._center.X - text.Width / 2, circle._center.Y - text.Height / 2));
            }
            else
                circle._text.Visibility = Visibility.Hidden;
        }

        private static void OnValueVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UpdateCircle(d as CircleProgressBar);
        }
    }
}
